import ast
import json
import jsonschema
from functools import wraps
import logging
from odoo.http import request, Response
from .encapsulation import valid_response, invalid_business, invalid_response, invalid_request
from odoo.exceptions import UserError
from urllib.parse import urlparse, parse_qs

_logger = logging.getLogger(__name__)


def exception_handler(func):
    """异常处理"""

    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)  # 调用原始函数

            return result
        except UserError as e:
            try:
                code = int(e.__str__())
                return invalid_business(message_code=code)
            except:
                return invalid_business(message=str(e))
        except Exception as e:
            return invalid_response(message=str(e))

    return wrapper


# def validate_token(func):
#     """ token校验 """
#
#     @wraps(func)
#     def wrapper(*args, **kwargs):
#         access_token = request.httprequest.headers.get("Authorization")
#         if not access_token:
#             return invalid_request(401, message="access_token_not_found ,missing access token in request header", )
#         access_token_data = (
#             request.env["api.access.token"].sudo().search([("token", "=", access_token)], order="id DESC", limit=1)
#         )
#
#         if access_token_data.find_one_or_create_token(customer_id=access_token_data.customer_id.id) != access_token:
#             return invalid_request(401, message="token seems to have expired or invalid", )
#
#         request.session.customerid = access_token_data.customer_id.id
#         request.customerid = access_token_data.customer_id.id
#         # 我们假定，所有需要token的地方必然是增删改，增删改需要管理员身份。管理员手动设置为1
#         request.uid = 1
#         return func(*args, **kwargs)
#
#     return wrapper


def validate_rp_api(schema=False, lang=False, token=False):
    """
    符合 rectaparcel 项目的API样式

    接口校验规范：
    1、目前只接受['GET','POST','PUT','DELETE']四种类型的接口
    2、无论哪种接口，Content-Type = application/x-www-form-urlencoded
    3、['GET','DELETE']接口中，参数必须放在URL中，并且参数不接受集合或者列表类型
    4、['POST','PUT']接口中，参数必须放在body中

    - schema: 需要进行json校验的schema,传入的是一个字典类型
        用于校验request的json是否格式正确
       工作原理：在'request_schema_json'中存放了每个接口需要的参数格式，获取json格式后，传给该装饰器，在函数前校验。如果不符合格式返回400

    - lang:  如果lang = True, 则表示该接口需要语言处理，并且需要在request.header中获取Accept-Language
    - token: 如果token = True, 则表示该接口需要进行token校验，并且需要在request.header中获取Authorization
    """

    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 请求方法
            method = str(request.httprequest.method).upper()
            # 请求数据类型
            content_type = request.httprequest.headers.get("Content-Type", False)
            content_type = (str(content_type).lower()).split(';')[0] if content_type else False
            url_params = parse_qs(urlparse(request.httprequest.url).query)
            # print('1',url_params)

            # 接口校验规范
            if method in ['GET', 'DELETE']:
                if content_type:
                    return invalid_request(415, message=" GET/DELETE接口中，不需要Content-Type")
                # if len(list(url_params.keys())) != len(kwargs.keys()):
                #     return invalid_request(400, message="从URL中获取的参数数量与接口解析获取的参数数量不一致，判断为在body中传值，但 GET/DELETE 接口不解析body中参数", )
                for param in url_params.values():
                    if all([isinstance(param, list), len(param) > 1]):
                        return invalid_request(400, message="该 GET/DELETE 接口中,不接受重复参数", )
                    if all([isinstance(param[0], str), is_valid_dict(param[0])]):
                        return invalid_request(400, message=" GET/DELETE 接口中,不接受参数类型为集合")
            elif method in ['POST', 'PUT']:
                if any([not content_type, content_type != 'application/x-www-form-urlencoded']):
                    return invalid_request(415, message=" POST/PUT接口中，没有获取到Content-Type,接口请求头中Content-Type请使用application/x-www-form-urlencoded")

                if url_params:
                    return invalid_request(400, message=" POST/PUT 接口中,不接受URL参数", )
            else:
                return invalid_request(405, message="目前接口只支持 ['GET','POST','PUT','DELETE'] ", )

            # 从请求头中获取语言
            if lang:
                local_lang = request.env.context.get('lang', 'en_US')  # 获取环境语言，如果没有则使用en_US
                req_lang = request.httprequest.headers.get("Accept-Language", local_lang)  # 获取请求方要求的语言，如果没有则使用环境语言
                request.lang = req_lang  # 将语言参数传给odoo环境

            # token校验
            if token:
                access_token = request.httprequest.headers.get("Authorization")
                if not access_token:
                    return invalid_request(401, message="access_token_not_found ,missing access token in request header", )
                access_token_data = (
                    request.env["api.access.token"].sudo().search([("token", "=", access_token)], order="id DESC", limit=1)
                )

                if access_token_data.find_one_or_create_token(customer_id=access_token_data.customer_id.id) != access_token:
                    return invalid_request(401, message="token seems to have expired or invalid", )

                request.session.customerid = access_token_data.customer_id.id
                request.customerid = access_token_data.customer_id.id
                # 我们假定，所有需要token的地方必然是增删改，增删改需要管理员身份。管理员手动设置为1
                request.uid = 1
            # print('预处理前',kwargs)
            # JSON格式转换(适配x-www-form-urlencoded导致的格式错误问题)
            try:
                if method in ['GET', 'DELETE']:
                    for k, v in kwargs.items():
                        print(v)
                        kwargs[k] = valid_list(v)
                if method in ['POST', 'PUT']:
                    kwargs = clean_nested_data(kwargs)
                    kwargs = json.loads(list(kwargs.keys())[0])
            except Exception as e:
                return invalid_request(400, message=" JSON格式预处理有误,请检验传入的数据", )
            # print('预处理',kwargs)
            # JSON校验
            if schema:
                # 格式化JSON数据
                kwargs = format_and_convert_json(kwargs, schema)
                # 校验格式化后的数据
                try:
                    jsonschema.validate(kwargs, schema)
                    # 将格式化后的数据传递给被装饰的函数
                    return func(*args, **kwargs)
                except jsonschema.exceptions.ValidationError as e:
                    return invalid_request(400, message='JSON数据检验失败，{},字段名:{}'.format(e.message, e.path))
                except json.JSONDecodeError as e:
                    return invalid_request(400, message='JSON数据检验失败，{}'.format(e))

            return func(*args, **kwargs)

        return wrapper

    return decorator


def clean_nested_data(data):
    if isinstance(data, dict):
        # 如果数据是字典，递归处理每个键值对
        cleaned_data = {}
        for key, value in data.items():
            cleaned_key = clean_nested_data(key)
            cleaned_value = clean_nested_data(value)
            cleaned_data[cleaned_key] = cleaned_value
        return cleaned_data
    elif isinstance(data, list):
        # 如果数据是列表，递归处理每个元素
        cleaned_data = []
        for item in data:
            cleaned_item = clean_nested_data(item)
            cleaned_data.append(cleaned_item)
        return cleaned_data
    elif isinstance(data, str):
        # 如果数据是字符串，去掉空格和\r\n
        return data.replace(' ', '').replace('\r', '').replace('\n', '')
    else:
        # 其他数据类型保持不变
        return data


def format_and_convert_json(data, schema):
    if isinstance(schema, dict) and schema.get('properties', False):
        formatted_data = {}
        for key, value in schema.get('properties', {}).items():
            if key in data:
                formatted_data[key] = format_and_convert_json(data[key], value)
        return formatted_data
    elif isinstance(schema, list):
        if isinstance(data, list):
            return [format_and_convert_json(item, schema[0]) for item in data]
        else:
            return None
    else:
        # 进行类型转换
        try:
            schema_type = schema['type'] if isinstance(schema['type'], str) else schema['type'][0]
            if schema_type == 'integer':
                if isinstance(data, str) and str(data).lower() in ["", "''", 'null', 'none']:
                    return None
                return int(data)
            elif schema_type == 'number':
                if isinstance(data, str) and str(data).lower() in ["", "''", 'null', 'none']:
                    return None
                return float(data)
            elif schema_type == 'string':
                return str(data) if data != "''" else ''
            elif schema_type == 'boolean':
                if isinstance(data, str):
                    if data.lower() == 'false':
                        return False
                    elif data.lower() == 'true':
                        return True
                else:
                    return data
            elif schema_type == 'null':
                return None
            else:
                return data
        except (ValueError, TypeError):
            return data


def is_valid_dict(data_str):
    """
    判断 形如 "{'fd':123}"、"['1111', '234234']" 能否转换为字典
    不能转换输出False
    能转换且为list，输出False
    能转换且为dict,输出True
    """
    try:
        # 尝试解析字符串为JSON对象
        parsed_data = ast.literal_eval(data_str)
        if isinstance(parsed_data, list):
            return False
        elif isinstance(parsed_data, dict):
            return True
        else:
            return False
    except (ValueError, SyntaxError, TypeError):
        return False


def valid_list(data_str):
    """ 尝试将字符串转换为列表，如果可行则输出列表，不可行返回原字符串 """
    try:
        parsed_data = ast.literal_eval(data_str)
        # print(parsed_data,type(parsed_data))
        if isinstance(parsed_data, list):
            return parsed_data
        else:
            return data_str
    except (ValueError, SyntaxError, TypeError):
        return data_str
